﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace NFramework.DataAccess
{
    public abstract class DataHelper<T> : IDataHelper, IDisposable 
        where T : DataHelper<T>, new()
    {

        #region [ Creation ]

        protected DataHelper() { IsRollingBackOpenTransactionsOnExecutionError = true; }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
        public static T Create(string procedure)
        {
            return Create(new Querying.SimpleQuery(procedure));
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
        public static T Create(Querying.IDataQuery query)
        {
            var helper = new T();
            helper.Command = DataAccessContext.Current.GetConnection().CreateCommand();
            query.InitializeHelper(helper);
            return helper;
        }

        #endregion

        #region [ IDataHelper Members ]

        public string Procedure
        {
            get { return Command.CommandText; }
            set { Command.CommandText = value; }
        }

        public CommandType CommandType
        {
            get { return Command.CommandType; }
            set { Command.CommandType = value; }
        }

        public ICommandParameterCollection Parameters
        {
            get 
            {
                if (_parameters == null)
                {
                    _parameters = CommandParameterCollection.CreateCollection(Command.Parameters);
                }
                return _parameters; 
            }
        }
        private ICommandParameterCollection _parameters;

        public void SetTimeout(int seconds)
        {
            _commandTimeout = seconds;
        }
        private int? _commandTimeout;

        #endregion

        #region [ Command ]

        protected virtual System.Data.Common.DbCommand Command
        {
            get
            {
                if (_command == null)
                {
                    throw new InvalidOperationException("Attempting to use an uninitialized DataHelper");
                }
                return _command;
            }
            set
            {
                _command = value;
            }
        }
        private System.Data.Common.DbCommand _command;

        protected void Open()
        {
            if (_commandTimeout.HasValue) {
                Command.CommandTimeout = _commandTimeout.Value;
            }
            if (Command.Connection.State != ConnectionState.Open) {
                Command.Connection.Open();
            }
        }

        protected TResult ExecuteHelper<TResult>(Func<TResult> executor)
        {
            try
            {
                Open();
                return executor.Invoke();
            }
            catch
            {
                if (IsRollingBackOpenTransactionsOnExecutionError)
                {
                    RollbackOpenTransaction();
                }
                throw;
            }
        }

        #endregion

        #region [ Error Handling ]

        /// <summary>
        ///     A flag for altering whether transactions are rolled back on error in Execution.
        /// </summary>
        /// <remarks>defaults to true.</remarks>
        public bool IsRollingBackOpenTransactionsOnExecutionError { get; set; }

        /// <summary>
        /// Rolls back any open transactions.
        /// </summary>
        protected void RollbackOpenTransaction() 
        {
            if (_command != null) 
            {
                Command.Parameters.Clear();
                Command.CommandType = CommandType.Text;
                Command.CommandText = "IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION";
                Command.ExecuteNonQuery();
            }
        }

        #endregion

        #region [ IDisposable Members ]

        private bool _isDisposed = false;

        protected virtual void Dispose(bool disposing) 
        {
            if (!_isDisposed) 
            {
                if (disposing && _command != null && Command.Connection != null) 
                {
                    Command.Connection.Close();
                }
            }
            _isDisposed = true;
        }

        public void Close()
        {
            Dispose(true);
        }

        void IDisposable.Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}
